#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QButtonGroup>

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
#if defined (Q_OS_ANDROID)    /* QComboBox在不同手机可能存在bug，表现为无法弹出下拉框，需要开关一下可编辑属性 */
    ui->protocolTypeComboBox->setEditable(true);
    ui->protocolTypeComboBox->setEditable(false);
#endif

    //    ui->recvTextEdit->viewport()->installEventFilter(this);
}

MainWindow::~MainWindow()
{
    tcpSocket.close();
    tcpServer.close();
    udpSocket.close();
    if(!connectedTcpClients.isEmpty())
    {
        foreach(QTcpSocket* socket, connectedTcpClients)
        {
            socket->close();
        }
        connectedTcpClients.clear();
    }
    delete ui;
}

void MainWindow::signalsConnect()
{
    connect(ui->clearRecvButton, &QPushButton::clicked, ui->recvTextEdit, &QTextEdit::clear);
    connect(ui->clearSendButton, &QPushButton::clicked, ui->sendTextEdit, &QTextEdit::clear);

    connect(ui->protocolTypeComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &MainWindow::protocolTypeChannged);

    connect(ui->remoteHostOperationButton, &QPushButton::clicked, this, [=](){
        int index = ui->remoteHostComboBox->currentIndex();
        if(index >= 0)
        {
            if(protocolType == 3)
            {
                connectedTcpClients[index]->close();
                connectedTcpClients.removeAt(index);
            }
            ui->remoteHostComboBox->removeItem(index);
        }
    });

    connect(ui->networkOperationButton, &QPushButton::clicked, this, &MainWindow::networkOperation);

    //    connect(ui->resetCountButton, &QPushButton::clicked, this, [=](){
    //        recvByteCount = 0;
    //        sendByteCount = 0;
    //        ui->statusbar->showMessage(tr("RX:%1   TX:%2").arg(recvByteCount).arg(sendByteCount));
    //    });

    ui->statusbar->showMessage(tr("RX:%1   TX:%2").arg(recvByteCount).arg(sendByteCount));

    connect(&udpSocket, &QUdpSocket::readyRead, this, &MainWindow::readDataFromUdp);
    connect(&tcpSocket, &QTcpSocket::readyRead, this, &MainWindow::readDataFromTcp);
    connect(&tcpSocket, &QTcpSocket::disconnected, this, &MainWindow::remoteHostDisconnected);
    connect(&tcpServer, &QTcpServer::newConnection, this, &MainWindow::newConnection);

    connect(ui->outputFileCheckBox, &QCheckBox::clicked, this, [=](){
        if(!ui->outputFileCheckBox->isChecked())
        {
            logFileName.clear();
        }else
        {
            QString filename = QFileDialog::getSaveFileName(this, tr("保存文件"), QApplication::applicationDirPath(), tr("Text (*.log *.txt)"));
            if(!filename.isEmpty())
            {
                logFileName = filename;
            }else
            {
                ui->outputFileCheckBox->setChecked(false);
            }
        }
    });

    connect(ui->sendButton, &QPushButton::clicked, this, &MainWindow::sendOperation);

    connect(ui->sendAsciiRadioButton, &QRadioButton::clicked, this, &MainWindow::sendDataFormatChannged);
    connect(ui->sendHexRadioButton, &QRadioButton::clicked, this, &MainWindow::sendDataFormatChannged);

    connect(&circleTimer, &QTimer::timeout, this, &MainWindow::sendOperation);

    connect(ui->circleSendCheckBox, &QCheckBox::clicked, this, [=](){
        if(ui->circleSendCheckBox->isChecked())
        {
            circleTimer.start(ui->circleSpinBox->value());
        }else
        {
            circleTimer.stop();
        }
    });

    QPushButton* button = new QPushButton(this);
    button->setText(tr("复位计数"));
    ui->statusbar->addPermanentWidget(button);
    connect(button, &QPushButton::clicked, this, [=]{
        recvByteCount = 0;
        sendByteCount = 0;
        ui->statusbar->showMessage(tr("RX:%1   TX:%2").arg(recvByteCount).arg(sendByteCount));
    });
}

bool MainWindow::eventFilter(QObject *obj, QEvent *event)
{
    //    if(obj == ui->recvTextEdit->viewport())
    //    {
    //        if(event->type() == QEvent::MouseButtonPress)
    //        {
    //            QMouseEvent* e = static_cast<QMouseEvent*>(event);
    //            if(e->button() == Qt::LeftButton)
    //            {
    //                ui->recvTextEdit->moveCursor(QTextCursor::End);
    //                return true;
    //            }
    //        }
    //    }
    return QWidget::eventFilter(obj, event);
}

void MainWindow::showEvent(QShowEvent *e)
{
    ui->protocolTypeComboBox->addItems({"UDP", "TCP Client", "TCP Server"});

    ui->protocolTypeComboBox->setCurrentIndex(Settings::protocolType());
    protocolTypeChannged(ui->protocolTypeComboBox->currentIndex());

    ui->ipComboBox->setCurrentIndex(ui->ipComboBox->findText(Settings::ip()));
    ui->portSpinBox->setValue(Settings::port());
    ui->recvAsciiRadioButton->setChecked(!Settings::recvHex());
    ui->recvHexRadioButton->setChecked(Settings::recvHex());
    ui->logDisplayCheckBox->setChecked(Settings::logDisplayMode());
    ui->outputFileCheckBox->setChecked(Settings::outputToFile());
    ui->autoNewlineCheckBox->setChecked(Settings::autoNewline());
    ui->hideRecvCheckBox->setChecked(Settings::hideRecv());
    ui->sendAsciiRadioButton->setChecked(!Settings::sendHex());
    ui->sendHexRadioButton->setChecked(Settings::sendHex());
    ui->addEnterCheckBox->setChecked(Settings::addEnter());
    ui->addNewlineCheckBox->setChecked(Settings::addNewline());
    ui->remoteHostComboBox->setEditText(Settings::remoteAddress());
    //    ui->recvTextEdit->setPlainText(Settings::recvText());
    ui->sendTextEdit->setPlainText(Settings::sendText());
    ui->circleSpinBox->setValue(Settings::circle());

    logFileName = Settings::logFilename();

    controlsEnable(true);

    signalsConnect();

    ui->networkOperationButton->setIcon(QIcon(":/icons/close.png"));

    setWindowIcon(QIcon(":/icons/icon.ico"));

    QWidget::showEvent(e);
}

void MainWindow::closeEvent(QCloseEvent *e)
{
    QWidget::closeEvent(e);

    Settings::setProtocolType(ui->protocolTypeComboBox->currentIndex());
    Settings::setIp(ui->ipComboBox->currentText());
    Settings::setPort(ui->portSpinBox->value());
    Settings::setRecvHex(ui->recvHexRadioButton->isChecked());
    Settings::setLogDisplayMode(ui->logDisplayCheckBox->isChecked());
    Settings::setOutputFile(ui->outputFileCheckBox->isChecked());
    Settings::setAutoNewline(ui->autoNewlineCheckBox->isChecked());
    Settings::setHideRecv(ui->hideRecvCheckBox->isChecked());
    Settings::setSendHex(ui->sendHexRadioButton->isChecked());
    Settings::setAddEnter(ui->addEnterCheckBox->isChecked());
    Settings::setAddNewline(ui->addNewlineCheckBox->isChecked());
    Settings::setRemoteAddress(ui->remoteHostComboBox->currentText());
    //    Settings::setRecvText(ui->recvTextEdit->toPlainText());
    Settings::setSendText(ui->sendTextEdit->toPlainText());
    Settings::setCircle(ui->circleSpinBox->value());
    Settings::setLogFilename(logFileName);
}

void MainWindow::protocolTypeChannged(int index)
{
    protocolType = index + 1;

    if(protocolType == 1)
    {
        ui->remoteHostGroupBox->show();

        ui->networkOperationButton->setText(tr("打开"));
        ui->remoteHostOperationButton->setText(tr("清除"));
        ui->remoteHostComboBox->setEditable(true);
    }else if (protocolType == 2)
    {
        ui->remoteHostGroupBox->hide();
        ui->networkOperationButton->setText(tr("连接"));
    }else if (protocolType == 3)
    {
        ui->remoteHostGroupBox->show();
        ui->networkOperationButton->setText(tr("监听"));
        ui->remoteHostOperationButton->setText(tr("断开"));
        ui->remoteHostComboBox->setEditable(false);
    }

    ui->ipComboBox->clear();

    if(protocolType == 1)
    {
        ui->ipComboBox->addItem("0.0.0.0");
    }

    foreach (QHostAddress address, QNetworkInterface::allAddresses())
    {
        if(address.protocol() == QAbstractSocket::IPv4Protocol)
        {
            //            if (address.toString().contains("127.0."))
            //            {
            //                continue;
            //            }
            //            else
            {
                ui->ipComboBox->addItem(address.toString());
            }
        }
    }
}

void MainWindow::networkOperation()
{
    if(protocolType < 1 || protocolType > 3)
        return;

    switch (protocolType)
    {
    case 1:  // UDP
    {
        if(isRunning)
        {
            udpSocket.close();
            isRunning = false;
            ui->remoteHostComboBox->clear();
            ui->networkOperationButton->setText(tr("打开"));
            controlsEnable(true);
        }else
        {
            if(!isIpAddress(ui->ipComboBox->currentText()))
            {
                QMessageBox::critical(this, tr("错误提示"), tr("IP设置有误，请检查！"));
                break;
            }

            if(!udpSocket.bind(QHostAddress(ui->ipComboBox->currentText()), ui->portSpinBox->value()))
            {
                QMessageBox::critical(this, tr("错误提示"), tr("绑定失败，请检查IP地址是否正确或端口是否被占用！"));
            }else
            {
                isRunning = true;
                ui->networkOperationButton->setText(tr("关闭"));
                controlsEnable(false);
            }
        }
        break;
    }
    case 2:  // TCP Client
    {
        if(isRunning)
        {
            tcpSocket.close();
            isRunning = false;
            ui->networkOperationButton->setText(tr("连接"));
            controlsEnable(true);
        }else
        {
            if(!isIpAddress(ui->ipComboBox->currentText()))
            {
                QMessageBox::critical(this, tr("错误提示"), tr("IP设置有误，请检查！"));
                break;
            }

            tcpSocket.connectToHost(QHostAddress(ui->ipComboBox->currentText()), ui->portSpinBox->value());
            setCursor(Qt::WaitCursor);
            if(tcpSocket.waitForConnected(3000))
            {
                isRunning = true;
                ui->networkOperationButton->setText(tr("断开"));
                controlsEnable(false);
            }else
            {
                QMessageBox::critical(this, tr("错误提示"), tr("连接超时，请检查IP和端口是否有误后重试！"));
            }
            setCursor(Qt::ArrowCursor);
        }
        break;
    }
    case 3:  // TCP Server
    {
        if(isRunning)
        {
            tcpServer.close();
            foreach(QTcpSocket* socket, connectedTcpClients)
            {
                socket->close();
            }
            connectedTcpClients.clear();
            ui->remoteHostComboBox->clear();
            isRunning = false;
            ui->networkOperationButton->setText(tr("监听"));
            controlsEnable(true);
        }else
        {
            if(!isIpAddress(ui->ipComboBox->currentText()))
            {
                QMessageBox::critical(this, tr("错误提示"), tr("IP设置有误，请检查！"));
                break;
            }

            if(tcpServer.listen(QHostAddress(ui->ipComboBox->currentText()), ui->portSpinBox->value()))
            {
                isRunning = true;
                ui->networkOperationButton->setText(tr("取消"));
                controlsEnable(false);
            }else
            {
                QMessageBox::critical(this, tr("错误提示"), tr("监听失败，请检查IP地址是否正确或端口是否被占用！"));
            }
        }
        break;
    }
    default:break;
    }

    if(isRunning)
    {
        ui->networkOperationButton->setIcon(QIcon(":/icons/open.png"));
    }else
    {
        ui->networkOperationButton->setIcon(QIcon(":/icons/close.png"));
    }
}

void MainWindow::controlsEnable(bool enabel)
{
    ui->protocolTypeComboBox->setEnabled(enabel);
    ui->ipComboBox->setEnabled(enabel);
    ui->portSpinBox->setEnabled(enabel);

    ui->sendButton->setEnabled(!enabel);
    ui->circleSendCheckBox->setEnabled(!enabel);
}

void MainWindow::showRecvText(const QString& time, const QString& address, const QByteArray& byteArray)
{
    QString head, tail;
    QString text;

    if(ui->logDisplayCheckBox->isChecked())
    {
        head = NEWLINE + tr("[%1]# Recv From <%2>:%3").arg(time).arg(address).arg(NEWLINE);
    }

    if(ui->recvAsciiRadioButton->isChecked())
    {
        QTextCodec* gbk = QTextCodec::codecForName("GBK");
        QString unicode = gbk->toUnicode(byteArray);
        text = QString::fromUtf8(unicode.toUtf8());
    }else
    {
        for(int i = 0; i < byteArray.size(); i++)
        {
            text += QString::asprintf("%02X ", byteArray[i] & 0XFF);
        }
    }

    if(ui->autoNewlineCheckBox->isChecked())
    {
        tail += NEWLINE;
    }

    if(!ui->hideRecvCheckBox->isChecked())
    {
        ui->recvTextEdit->moveCursor(QTextCursor::End);
        if(ui->logDisplayCheckBox->isChecked())
        {
            ui->recvTextEdit->setTextColor(QColor(Qt::gray));
            ui->recvTextEdit->insertPlainText(head);
            ui->recvTextEdit->setTextColor(QColor("#00A000"));
            ui->recvTextEdit->insertPlainText(text);
            ui->recvTextEdit->setTextColor(QColor(Qt::black));
            ui->recvTextEdit->insertPlainText(tail);
        }else
        {
            ui->recvTextEdit->insertPlainText(head + text + tail);
        }
    }

    if(ui->outputFileCheckBox->isChecked())
    {
        QFile file(logFileName);
        if(file.open(QIODevice::WriteOnly | QIODevice::Append))
        {
            QString s = head + text + tail;
            QByteArray bytes = s.toLocal8Bit();
            file.write(bytes, bytes.size());
            file.close();
        }
    }

    recvByteCount += byteArray.size();

    ui->statusbar->showMessage(tr("RX:%1   TX:%2").arg(recvByteCount).arg(sendByteCount));
}

void MainWindow::readDataFromTcp()
{
    QTcpSocket* socket = qobject_cast<QTcpSocket *>(sender());
    QString address = tr("%1:%2").arg(socket->peerAddress().toString()).arg(socket->peerPort());
    showRecvText(QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss:zzz"), address, socket->readAll());
}

void MainWindow::readDataFromUdp()
{
    QUdpSocket* socket = qobject_cast<QUdpSocket *>(sender());

    while(socket->hasPendingDatagrams())
    {
        qint64 size = socket->pendingDatagramSize();
        QByteArray data(size, 0);
        QHostAddress addr;
        quint16 port;
        if(socket->readDatagram(data.data(), size, &addr, &port) >= 0)
        {
            QString address = QString("%1:%2").arg(addr.toString()).arg(port);
            if(ui->remoteHostComboBox->findText(address) < 0)
            {
                ui->remoteHostComboBox->addItem(address);
            }
            showRecvText(QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss:zzz"), address, data);
        }
    }
}

void MainWindow::newConnection()
{
    QTcpServer* server = qobject_cast<QTcpServer *>(sender());
    while(server->hasPendingConnections())
    {
        QTcpSocket* socket = server->nextPendingConnection();
        connectedTcpClients.append(socket);
        QString address = tr("%1:%2").arg(socket->peerAddress().toString()).arg(socket->peerPort());
        if(ui->remoteHostComboBox->findText(address) < 0)
        {
            ui->remoteHostComboBox->addItem(address);
        }
        connect(socket, &QTcpSocket::readyRead, this, &MainWindow::readDataFromTcp);
        connect(socket, &QTcpSocket::disconnected, this, &MainWindow::remoteHostDisconnected);
    }
}

void MainWindow::sendOperation()
{
    if(isRunning)
    {
        qint64 count = 0;

        QString text = ui->sendTextEdit->toPlainText();
        QString echotext = text;

        QByteArray data;

        if(ui->sendHexRadioButton->isChecked())
        {
            text.remove(" ");
            data = QByteArray(text.length() / 2, 0);
            str2hex(text.toStdString().c_str(), text.length(), (uint8_t *)data.data());
        }else
        {
            QTextCodec* codec = QTextCodec::codecForName("GBK");
            data = codec->fromUnicode(text);
        }

        if(ui->addEnterCheckBox->isChecked())
        {
            data.append('\r');
        }

        if(ui->addNewlineCheckBox->isChecked())
        {
            data.append('\n');
        }

        switch (protocolType) {
        case 1:
        {
            QStringList address = ui->remoteHostComboBox->currentText().split(":");
            if(address.size() == 2)
            {
                if(!isIpAddress(address[0]))
                {
                    QMessageBox::critical(this, tr("错误提示"), tr("远程主机的IP或端口设置有误，请检查！"));
                    break;
                }
                count = udpSocket.writeDatagram(data.data(), data.size(), QHostAddress(address[0]), address[1].toInt());
                if(count > 0)
                {
                    sendByteCount += count;
                }
            }else
            {
                QMessageBox::critical(this, tr("错误提示"), tr("远程主机的IP或端口设置有误，请检查！"));
            }
            break;
        }
        case 2:
        {
            count = tcpSocket.write(data);
            if(count > 0)
            {
                sendByteCount += count;
            }
            break;
        }
        case 3:
        {
            if(!connectedTcpClients.isEmpty())
            {
                QTcpSocket* socket = connectedTcpClients[ui->remoteHostComboBox->currentIndex()];
                count = socket->write(data);
                if(count > 0)
                {
                    sendByteCount += count;
                }
            }
            break;
        }
        }

        if(count != 0 && ui->logDisplayCheckBox->isChecked())
        {
            QString address;
            if(protocolType == 1 || protocolType == 3)
            {
                address = ui->remoteHostComboBox->currentText();
            }else if(protocolType == 2)
            {
                address = ui->ipComboBox->currentText() + ":" + QString::asprintf("%d", ui->portSpinBox->value());
            }
            QString time = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss:zzz");
            QString head = NEWLINE + tr("[%1]# Send To <%2>").arg(time).arg(address) + NEWLINE;
            ui->recvTextEdit->moveCursor(QTextCursor::End);
            ui->recvTextEdit->setTextColor(QColor(Qt::gray));
            ui->recvTextEdit->insertPlainText(head);
            ui->recvTextEdit->setTextColor(QColor("#0000FF"));
            ui->recvTextEdit->insertPlainText(echotext);
            ui->recvTextEdit->setTextColor(QColor(Qt::black));
        }

        ui->statusbar->showMessage(tr("RX:%1   TX:%2").arg(recvByteCount).arg(sendByteCount));
    }
}

/**
 * 十六进制字符转十六进制数，例：'A' -> 0xA
 * @param  chr 要转换的十六进制字符
 * @return     转换后的值
 */
uint8_t MainWindow::chr2hex(uint8_t chr)
{
    if (chr >= '0' && chr <= '9')return chr - '0';
    if (chr >= 'A' && chr <= 'F')return (chr - 'A' + 10);
    if (chr >= 'a' && chr <= 'f')return (chr - 'a' + 10);
    return 0;
}

/**
 * 十六进制数转为字符串形式，例：0x0A->'A'
 * @param  hex 要转换的数，范围0~F
 * @return     转换后的值
 */
uint8_t MainWindow::hex2chr(uint8_t hex)
{
    if (hex <= 9)return hex + '0';
    if (hex >= 10 && hex <= 15)return (hex - 10 + 'A');
    return '0';
}

/**
 * 十六进制字符串转数值，例："AABBCC" -> 0XAA,0XBB,0XCC
 * @param str 待转换的十六进制字符串
 * @param len 字符串长度
 * @param hex 存放转换后的字符串值
 */
void MainWindow::str2hex(const void* str, int len, uint8_t* hex)
{
    const uint8_t* s = (const uint8_t *)str;
    uint8_t* d = (uint8_t *)hex;
    while (len > 0)
    {
        *d = (chr2hex(s[0]) << 4) | chr2hex(s[1]);
        d += 1;
        s += 2;
        len -= 2;
    }
}

void MainWindow::sendDataFormatChannged()
{
    QRadioButton* button = qobject_cast<QRadioButton*>(sender());

    QString srctext = ui->sendTextEdit->toPlainText();
    QString dsttext;

    if(button == ui->sendHexRadioButton)
    {
        QTextCodec* codec = QTextCodec::codecForName("GBK");
        QByteArray data = codec->fromUnicode(srctext);
        for(int i = 0; i < data.size(); i++)
        {
            dsttext += QString::asprintf("%02X ", data[i] & 0XFF);
        }
    }else
    {
        srctext.remove(" ");
        QByteArray data(srctext.length() / 2, 0);
        str2hex(srctext.toStdString().c_str(), srctext.length(), (uint8_t *)data.data());
        QTextCodec *gbk = QTextCodec::codecForName("GBK");          //获取GBK编码对象
        QString unicode = gbk->toUnicode(data);                     //转UNICODE
        dsttext = QString::fromUtf8(unicode.toUtf8());
    }

    ui->sendTextEdit->setText(dsttext);
}

void MainWindow::remoteHostDisconnected()
{
    QTcpSocket* socket = qobject_cast<QTcpSocket *>(sender());

    if(protocolType == 2)
    {
        socket->close();
        isRunning = false;
        ui->networkOperationButton->setText(tr("连接"));
        controlsEnable(true);
    }else if (protocolType == 3)
    {
        socket->close();
        int index = connectedTcpClients.indexOf(socket);
        connectedTcpClients.removeAt(index);
        ui->remoteHostComboBox->removeItem(index);
    }
}

bool MainWindow::isIpAddress(const QString &ip)
{
    QRegExp exp("((2[0-4]\\d|25[0-5]|[01]?\\d\\d?)\\.){3}(2[0-4]\\d|25[0-5]|[01]?\\d\\d?)");
    return exp.exactMatch(ip);
}
